<?php
namespace bdhert\Tally;

use bdhert\Tally\exception\SignExpiredException;
use bdhert\Tally\exception\SignInvalidException;
use bdhert\Tally\join\JoinMode;
use bdhert\Tally\sign\Sign;
use bdhert\Tally\string\HandlerIterator;
use Webman\Http\Request;

/**
 * API简易时效签名 简易、有时效
 * Class APISign
 * @package bdhert\Tally
 */
class APISign {
    public string $account;

    private Request $request;
    private Config $config;
    private Signer $signer;
    private Joiner $joiner;

    /**
     * APISign constructor.
     * @param string $account
     */
    public function __construct(string $account = '') {
        [$this->request, $this->config, $this->account] = [request(), new Config($account), $account];

        $this->signer = Sign::Opt($this->config);
        $this->joiner = JoinMode::Opt($this->config, $this->request);
    }

    /**
     * 验签
     * @return bool
     */
    public function check() {
        [$sign, $time, $nonce] = [
            $this->request->header($this->config['sign_key']),
            $this->request->header($this->config['time_key']),
            $this->request->header($this->config['nonce_key'])
        ];
        if (empty($sign) || empty($time) || empty($nonce)) {
            throw new SignInvalidException('缺少必要参数', 400);
        }

        if (!$this->checkTTL($time)) throw new SignExpiredException('请求不在有效时间范围内', 400);

        $data = $this->joiner->getData();
        [$data[$this->config['nonce_key']], $data[$this->config['time_key']]] = [$nonce, $time];

        if (!$this->comparison($data, $sign)) throw new SignInvalidException('签名错误', 400);

        return true;
    }

    /**
     * 验证请求时效
     * @param $date
     * @return bool
     */
    private function checkTTL($date) {
        if ($this->config['ttl'] < 1) return true;
        if (empty($date)) return false;

        [$time, $now] = [is_numeric($date) ? (int)$date : strtotime($date), time()];

        return !($time === false || ($now - $time) > $this->config['ttl'] || $time > $now + $this->config['future']);
    }

    /**
     * 签名比对
     * @param array $params
     * @param string $sign
     * @return bool
     */
    private function comparison(array $params, string $sign): bool {
        $comparisonIterator = new HandlerIterator($this->config, $params, ['specialchars', 'stripslashes']);

        while ($comparisonIterator->valid()) {
            if ($this->signer->check($comparisonIterator->getParams(), $sign)) return true;

            $comparisonIterator->next();
        }

        return false;
    }
}